using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;

namespace PMCSerialTransfer
{
    
    /// <summary>
    /// C#.Net 2.0 wrapper around Parallax's Jeff Martins's Propellent library. See documentations by Jeff!
    /// </summary>
    public class Propellent : IDisposable
    {
        
        #region Private
        private bool m_LoadedLibary = false;
        #endregion Private
        
        #region struct/enums
        public struct PropellerVerson
        {
            public int ComPort;
            public int PropVerson;
        }
        public struct CompilerError
        {
            public string ScrFile;
            public uint ScrStart;
            public uint ScrCount;
            public string Error;
        }
        public enum LoadType : byte
        {
            RAM = 1,
            EEPROM = 2,
            EEPROM_RUN = 3
        }
        public enum ResetSig : byte
        {
            DTR = 0,
            RTS = 1,
            Both = 2
            //0 = toggle DTR (default), 1 = toggle RTS, 2 = toggle both DTR and RTS
        }
        public enum FileType : int
        {
            source = 0,
            data = 1
        }
        public enum FolderType : int
        {
            unused = 0,
            work = 1,
            library = 2,
            dual = 3
        }
        #endregion struct/enums

        #region API Calls
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern void InitPropellent(IntPtr WinHandel, bool StorePrefs, IntPtr PChar);
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern void FinalizePropellent();
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern uint GetLibraryVersion();
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern ResetSig GetResetSignal();
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern void SetResetSignal(ResetSig Value);
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern uint GetSerialSearchMethod();
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern void SetSerialSearchMethod(uint Value);
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern StringBuilder GetSerialSearchRules();
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern void SetSerialSearchRules(StringBuilder Value);
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern StringBuilder GetLibraryPath();
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern void SetLibraryPath(StringBuilder Value);
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern void ShowEditPorts();
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern StringBuilder GetPorts(bool Scannable);
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr CompileSource(StringBuilder Filename, bool ShowError);
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern StringBuilder CompileSourceDocs(StringBuilder Filename);
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern IntPtr GetSourceHierarchy();
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern bool SaveImage(bool AsBinary, StringBuilder Filename, bool ShowSaveAs);
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern uint GetPropellerVersion();
        
        [DllImport("Propellent.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
        private static extern void DownloadToPropeller(StringBuilder Filename, LoadType DownloadCmd);
        #endregion API Calls
        
        #region De/Constructor

        /// <summary>
        /// REQUIRED. This routine initializes critical internal items for proper library function.
        /// </summary>
        /// <param name="Winhandel">Handle to calling application's main window.</param>
        /// <param name="StorePrefs">True = Propellent library automatically stores its preference items in the Windows registry for use by future
        /// sessions. (See RegPath for default path). False = Propellent library does not save its settings; calling
        /// application is responsible for maintaining preference items.</param>
        /// <param name="PChar">May be null (default) or a pointer to a string indicating the registry path in which to automatically store
        /// preference items. This value is ignored if StorePrefs = False.
        /// Set RegPath to null to use the default path. Set RegPath to the address of a path string to have the library
        /// automatically store its settings in another path (such as your application's registry path); a new key will
        /// automatically be appended to the path.
        /// The default path is 'rootkey'\SOFTWARE\ParallaxInc\Propeller\Propellent\'lib_reg_ver' where 'rootkey' is
        /// HKEY_CURRENT_USER, and lib_reg_ver is the library's registry version (ex: 1.0).
        /// If not null, path must be a valid path within the HKEY_CURRENT_USER hive; do not include
        /// "HKEY_CURRENT_USER\" in the path. If it doesn't already exist, the path plus the 'lib_reg_key' will be
        /// created automatically at the end of a session if a setting had been modified within that session.</param>
        public Propellent(IntPtr Winhandel, bool StorePrefs, string PChar)
        {
            
            try
            {
                StringBuilder tmpSP = new StringBuilder(PChar);
                InitPropellent(Winhandel, StorePrefs, IntPtr.Zero);

                tmpSP = new StringBuilder(Microsoft.Win32.Registry.CurrentUser.OpenSubKey(@"Software\ParallaxInc\Propeller\1.0").GetValue("LibraryPaths").ToString());
                SetLibraryPath(tmpSP);
            }
            catch
            {
                m_LoadedLibary = false;
                return;
            }
            
            m_LoadedLibary = true;

        }

        ~Propellent()
        {
        }

        #region IDisposable Members

        /// <summary>
        /// EQUIRED. This routine cleans up internal items, like memory, used by the library. This must be called just before unloading the Propellent
        /// Library (if dynamically linked), or just before application termination (if statically linked).
        /// </summary>
        public void Dispose()
        {
            try
            {
                if (this.m_LoadedLibary)
                    FinalizePropellent();
            }
            catch { }
        }

        #endregion

        #endregion De/Constructor
        
        #region Get/Set

        /// <summary>
        /// OPTIONAL. Call this to retrieve the Propellent Library's version
        /// </summary>
        public uint LibraryVerson
        {

            get
            {
                if (!m_LoadedLibary)
                    return 0;
                return GetLibraryVersion();
            }
        }

        /// <summary>
        /// OPTIONAL. Get/Set Propeller chip reset signal preference. The default (DTR) will work for all true serial ports and for most USB-to-Serial
        /// devices, however some USB-to-Serial devices only support RTS (not DTR). This option allows the user to set which pin (DTR, RTS, or both)
        /// is used to reset the Propeller chip at the start of the identification or download process. This setting is automatically saved and restored
        /// between sessions by the Propellent library if InitPropellent was called with StorePrefs = True.
        /// 0 = toggle DTR (default), 1 = toggle RTS, 2 = toggle both DTR and RTS.
        /// </summary>
        public ResetSig ResetSignal
        {
            get
            {
                if (!m_LoadedLibary)
                    return 0;
                return GetResetSignal();
            }
            set
            {
                if (!m_LoadedLibary)
                    return;
                SetResetSignal(value);
            }
        }

        /// <summary>
        /// OPTIONAL. Get serial search method preference. This setting is automatically saved and restored between sessions by the Propellent library if InitPropellent was called with
        /// StorePrefs = True.
        /// 0 = Auto (scan all available ports), 1..n = Scan only one port (COM1..COMn).
        /// </summary>
        public uint SerialSearchMethod
        {
            get
            {
                if (!m_LoadedLibary)
                    return 0;
                return GetSerialSearchMethod();
            }
            set
            {
                if (!m_LoadedLibary)
                    return;
                SetSerialSearchMethod(value);
            }
        }

        /// <summary>
        /// OPTIONAL. Call this to set the serial search rules preference if Propellent library is configured to not store its own preferences (ie:
        /// InitPropellent was called with StorePrefs = False). If Propellent library is configured to store its own preferences (recommended), simply
        /// ignore the Set/Get- SerialSearchRules routines. The format and content of the serial search rules preference is all handled by the Propellent
        /// library; do not edit the value, but rather, call this procedure if you wish to manually set it to a value saved from a previous session. To get the
        /// current setting call the GetSerialSearchRules function. This setting is automatically saved and restored between sessions by the Propellent
        /// library if InitPropellent was called with StorePrefs = True.
        /// </summary>
        /// <param name="Value">string containing the serial search rules to use.</param>
        public string SerialSearchRules
        {
            get
            {
                if (!m_LoadedLibary)
                    return null;
                return GetSerialSearchRules().ToString();
            }
            set
            {

                if (!m_LoadedLibary)
                    return;

                StringBuilder tmpSP = new StringBuilder(value);
                SetSerialSearchRules(tmpSP);
            }
        }

        /// <summary>
        /// REQUIRED IF USING COMPILE FEATURES. Call this to set the path to the Propeller Library files that will be used for source code
        /// compiling operations. The library path must be set before calling CompileSource. If library path was set in a prior session, and InitPropellent
        /// was called with StorePrefs = True, there is no need to call SetLibraryPath unless the path needs to be changed.
        /// </summary>
        /// <param name="Value">string containing the fully-qualified path to the Propeller library; example: C:\Program Files\Parallax Inc\Propeller Tool</param>
        public string LibraryPath
        {
            get
            {
                if (!m_LoadedLibary)
                    return null;
                return GetLibraryPath().ToString();
            }
            set
            {

                if (!m_LoadedLibary)
                    return;

                StringBuilder tmpSP = new StringBuilder(value);
                SetLibraryPath(tmpSP);
            }
        }

        #endregion
        
        #region public functions
        /// <summary>
        /// OPTIONAL. Call this to compile a Propeller application from source code. Returns 0 (null) if compile was successful; returns non-zero if an
        /// error occurred. After a successful compile, call DownloadToPropeller with a null Filename parameter to download this compiled Propeller
        /// application to the Propeller chip. NOTE: Propeller source files may be either ANSI-encoded (one byte per character) or Unicode-encoded
        /// (UTF-16; two bytes per character).
        /// </summary>
        /// <param name="filename">This parameter must either be null or must be a pointer to a zero-terminated string containing the fully-qualified
        /// path to a folder or to a file that is the top object (source code) to compile. The Propellent Library will
        /// automatically load and compile any subobjects called for in the Filename object as needed.
        /// Filename not null: If Filename indicates a path to a folder, an Open dialog will appear (with that folder selected)
        /// to allow the user to choose a source file to be compiled. NOTE: To specify a folder, Filename must end with a
        /// trailing path delimiter '\'; for example: C:\Temp\ indicates the Temp folder on the C drive. If Filename indicates
        /// a valid path and file, that file will be opened automatically (no dialog will appear) and will be compiled.
        /// Filename = null: An open dialog will appear with the last-accessed folder selected to allow the user to choose a
        /// source file to be compiled.</param>
        /// <param name="ShowError">True = on error, show an error dialog before returning to caller. False = on error, immediately return to caller.</param>
        /// <returns>PropellentNet.Propellent.CompilerError structure</returns>
        public CompilerError CompileSource(string filename, bool ShowError)
        {

            CompilerError errStruct = new CompilerError();
            errStruct.Error = "Libary not loaded";
            
            if (!m_LoadedLibary)
                return errStruct;
            
            StringBuilder holdSB = new StringBuilder(filename);

            IntPtr errorPtr = CompileSource(holdSB, false);
            try
            {
                errStruct = (CompilerError)Marshal.PtrToStructure(errorPtr, typeof(CompilerError));
            }
            catch 
            {
                errStruct.Error = "";
            }

            return errStruct;
            
        }
        
        /// <summary>
        /// OPTIONAL. Call this after a successful call to CompileSource to save the compiled Propeller Application as a binary or EEPROM image.
        /// </summary>
        /// <param name="AsBinary">True = save image as a .binary file. False = save image as a .eeprom file.</param>
        /// <param name="Filename">A pointer to a zero-terminated string indicating the fully qualified path and filename for the image file to create. This path and filename will appear in the Save As dialog, if ShowSaveAs is True. Path and filename may be the source application's file name, in which case the extension will be replaced with either .binary or .eeprom accordingly.</param>
        /// <param name="ShowSaveAs">True = show Save As dialog (containing Filename's path and file) for user to make adjustments as desired. False = do not show Save As dialog; Filename must be a valid path and filename in this case.</param>
        /// <returns></returns>
        public bool SaveImage(bool AsBinary, string Filename, bool ShowSaveAs)
        {

            if (!m_LoadedLibary)
                return false;
            
            StringBuilder tmpSP = new StringBuilder(Filename);
            return SaveImage(AsBinary, tmpSP, ShowSaveAs);
        }
        
        /// <summary>
        /// OPTIONAL. Call this to display the Serial Port Search List dialog. This dialog describes the computer's serial ports and the order in which
        /// the library will search them when scanning for a Propeller chip. The user is able to reorder the list of ports and can create simple or advanced
        /// rules indicating which serial ports to include or exclude for scanning. The library manages the settings created by the user via this dialog and,
        /// if InitPropellent was called with StorePrefs = True, automatically stores those settings in the Windows registry for persistance across sessions.
        /// </summary>
        public void ShowEditPortDialog()
        {

            if (!m_LoadedLibary)
                return;
            
            ShowEditPorts();
        }
        
        /// <summary>
        /// OPTIONAL. Call this to receive a pointer to a string containing a comma-delimited list of current serial ports on the computer. Call this at any
        /// time to get an updated list since the available serial ports can change at any time as USB-to-Serial devices are connected/disconnected from
        /// the system. NOTE: After calling this function, be sure to copy the returned string to the caller's memory space before executing
        /// other Propellent functions which return a PChar.
        /// </summary>
        /// <param name="Scannable">True = (recommended) string is filtered per preferences to include only desirable ports. False = string is
        /// unfiltered; shows every serial port currently on the computer in the order given by the system.</param>
        /// <returns>string</returns>
        public string GetComPorts(bool Scannable)
        {

            if (!m_LoadedLibary)
                return null;
            
            return GetPorts(Scannable).ToString();
        }
        
        /// <summary>
        /// OPTIONAL. Call this to compile and retrieve documentation for source file given by Filename. Returns 0 (null) if failed; returns pointer to
        /// zero-terminated document string if successful.
        /// </summary>
        /// <param name="Filename">This parameter must either be null or must be a pointer to a zero-terminated string containing the fully-qualified
        /// path to a folder or to a file to retrieve the document for.
        /// Filename not null: If Filename indicates a path to a folder, an Open dialog will appear (with that folder selected)
        /// to allow the user to choose a source file to retrieve the document for. NOTE: To specify a folder, Filename
        /// must end with a trailing path delimiter '\'; for example: C:\Temp\ indicates the Temp folder on the C drive. If
        /// Filename indicates a valid path and file, that file will be opened automatically (no dialog will appear).
        /// Filename = null: An open dialog will appear with the last-accessed folder selected to allow the user to choose a
        /// source file to retrieve documentation from.</param>
        /// <returns>Null if failed and a string containing the document if true</returns>
        public string CompileSourceDocs(string Filename)
        {

            if (!m_LoadedLibary)
                return null;
            
            StringBuilder tmpSP = new StringBuilder(Filename);
            return CompileSourceDocs(tmpSP).ToString();
        }
        
        /// <summary>
        /// OPTIONAL. Call this after a successful call to CompileSource to retrieve the structure of the Propeller Application just compiled. The
        /// returned value is a pointer to a contiguous array of elements representing the objects of the application from the first object (the top object) to
        /// the last object (the last one referenced). Each element of the array returned consists of the following:
        /// </summary>
        /// <returns>1 byte = Indicates the attributes (type, location, horizontal level) of the object in the hierarchy.
        /// Bit 6 : 0 = source file, 1 = data file
        /// Bits 5:4 : 00 = unused, 01 = work folder, 10 = library folder, 11 = dual (exists in both work and
        /// library folders)
        /// Bits 3:0 : Horizontal level of object (0 through 15), where 0 is the left-most level (the top object)
        /// and 1 through 15 are deeper levels (nestings) of child objects.
        /// n bytes = full path and file name as a zero-terminated string
        /// 1 byte = zero-terminator for the above string
        /// The end of the array is denoted by one last element that consists of just 2 bytes, both equal to zero.</returns>
        public System.Data.DataSet GetPropSourceHierarchy()
        {

            if (!m_LoadedLibary) /// Double check that the Libary was loaded successfuly
                return null;

            System.Data.DataSet tmpDS = new System.Data.DataSet("Hierarchy"); /// Lets Build a dataset to return the structure in a nicer mannor than binary
            System.Data.DataTable tmpDT = new System.Data.DataTable("tblSourceHierarchy");
            tmpDT.Columns.Add("FileName", typeof(string)); /// I could have made the first two files into a single field but what the hey, I'll be nice to you developers and break it apart for you.
            tmpDT.Columns.Add("FolderPath", typeof(string));
            tmpDT.Columns.Add("HLevel", typeof(int)); /// Horizontal level
            tmpDT.Columns.Add("FolderType", typeof(FolderType)); /// 00 = unused, 01 = work folder, 10 = library folder, 11 = dual (exists in both work and library folders)
            tmpDT.Columns.Add("FileType", typeof(FileType)); /// 0 = source file, 1 = data file
            
            tmpDS.Tables.Add(tmpDT);

            char[] holdArray;

            try
            {
                holdArray = Marshal.PtrToStringAnsi(GetSourceHierarchy(), 10000).ToCharArray(); /// Pull 10000 chars from memory at the pointer returned from GetSourceHierarchy() API call
            }
            catch 
            {
                return null;
                /// Not sure whats going on here. Looks like maybe a bug in the DLL?
            }

            int lastRow = 0;
            string holdPath = "";

            for (int i = 0; i < 10000; i++)
            {
                

                if (holdArray[i] == '\0') /// Loop untill I hit the end of each row
                {
                    
                    object[] tmpRow = new object[5];
                    tmpRow[0] = holdPath.Substring(holdPath.LastIndexOf('\\')+1, holdPath.Length - holdPath.LastIndexOf('\\')-1);
                    tmpRow[1] = holdPath.Replace("\\" + (string)tmpRow[0], "");
                    tmpRow[2] = holdArray[lastRow] & 15;
                    tmpRow[3] = (FileType)((holdArray[lastRow] & 48) >> 4);
                    tmpRow[4] = (FolderType)((holdArray[lastRow] & 64) >> 6);
                    
                    tmpDT.Rows.Add(tmpRow);
                    
                    lastRow = i+1;
                    holdPath = "";
                    
                    if (holdArray[i + 1] == '\0' && holdArray[i + 2] == '\0') /// check to see if this was the last row
                        break;
                }
                else if (i > lastRow) /// After the first byte of earch row I can pull the file path
                    holdPath += holdArray[i].ToString();
            }

            return tmpDS;
        }
        
        /// <summary>
        /// OPTIONAL. Scan serial ports for a Propeller chip and display and return port and version of the Propeller chip, if found, or an error message, if not found.
        /// </summary>
        /// <returns>PropellentNet.Propellent.PropellerVerson structure</returns>
        public PropellerVerson GetPropVersion()
        {
            
            PropellerVerson tmpPV = new PropellerVerson();
            tmpPV.ComPort = -1;
            tmpPV.PropVerson = -1;
            
            if (!m_LoadedLibary)
                return tmpPV;
            
            uint holdVerson = GetPropellerVersion();
            
            if (holdVerson == 0)
                return tmpPV;
            
            tmpPV.ComPort = (int)holdVerson >> 16;
            tmpPV.PropVerson = (int)holdVerson & 65535;
            
            return tmpPV;
            
        }
        
        /// <summary>
        /// OPTIONAL. Scan serial ports for a Propeller chip and download a compiled Propeller Application to it. The "application" may be either an existing image file or the last successfully compiled application.
        /// </summary>
        /// <param name="Filename">This parameter must either be null or must be a pointer to a zero-terminated string containing the fully-qualified
        /// path to a folder or file.
        /// Filename not null: If Filename indicates a path to a folder, an Open dialog will appear (with that folder selected)
        /// to allow the user to choose an image file to be downloaded. NOTE: To specify a folder, Filename must end
        /// with a trailing path delimiter '\'; for example: C:\Temp\ indicates the Temp folder on the C drive. If Filename
        /// indicates a valid path and file, that file will be opened automatically (no dialog will appear) and will be
        /// downloaded to the Propeller.
        /// Filename = null: The image of the last successfully compiled application (via a call to CompileSource) will be
        /// downloaded to the Propeller.</param>
        /// <param name="DownloadCmd">1 = write to RAM and run, 2 = write to EEPROM and stop, 3 = write to EEPROM and run. NOTE: command 2 is not currently used by the Propeller Tool software.</param>
        public void DownloadToPropeller(string Filename, LoadType LoadType)
        {

            if (!m_LoadedLibary)
                return;
            
            StringBuilder tmpSP = new StringBuilder(Filename);
            DownloadToPropeller(tmpSP, LoadType);
        }
        #endregion public functions
        
    }
    
}